/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
 * PARTICULAR PURPOSE.
 * ***************************************************************************/


#include "stdafx.h"
#include <pgl/core/PGLBar.h>
#include <pgl/core/PGLDataVectors.h>
#include <pgl/core/DataRegistry.h>

PGL_IMPLEMENT_DYNCREATE(CPGLBar, CPGLObject);

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

//! Save settings
void CPGLBar::SerializeXML(CPGLArchive& ar)
{
	static const TCHAR szTag[] = _T("CPGLBar");
	std::_tstring str;
	CString strPos;	strPos.Format(_T("%u"),(DWORD)this);
	CPGLLine1DPtr pLine;

	markup::CMarkupArchive& xml = *((markup::CMarkupArchive*)ar.GetMarkup());
	if(ar.IsStoring())
	{
		xml.AddChildElem(szTag);
		xml.AddChildAttrib(_T("Name"),GetName());
		xml.IntoElem();

			/////////////////////////////////////////////////////////
			// Adding base class
			CPGLObject::SerializeXML(ar);

			xml.AddChildElemEx(_T("PixelInter"), m_dPixelInter);

			xml.AddChildElem(_T("Series"));
			xml.IntoElem();
				// savings lines
				CPGLLine1DList::iterator il;
				for (il= m_lSeries.begin(); il!= m_lSeries.end();il++)
				{
					pLine = *il;					
					pLine->SerializeXML(ar);
				}
			xml.OutOfElem();
			
		xml.OutOfElem();
	}
	else
	{
		Clear();

		if  (!xml.FindChildElem(szTag))
				return;

		xml.IntoElem();
			/////////////////////////////////////////////////////////
			// Adding base class
			CPGLObject::SerializeXML(ar);

			xml.FindGetChildData(_T("PixelInter"), m_dPixelInter);
			
			xml.ResetChildPos();
			if (xml.FindChildElem(_T("Series")))
			{
				xml.IntoElem();
					// saving position
					xml.SavePos(strPos);
					while (xml.FindChildElem(_T("CPGLLine1D")))
					{
						pLine = CPGLLine1DPtr(new CPGLLine1D);	

						xml.RestorePos(strPos);

						pLine->SerializeXML(ar);
						PushFront(pLine);

						// saving position
						xml.SavePos(strPos);
					}
				xml.OutOfElem();
			}

		xml.OutOfElem();
	}

}


#ifdef _DEBUG
void CPGLBar::Dump( CDumpContext& dc ) const
{
    // call base class function first
    CPGLObject::Dump( dc );

    // now do the stuff for our specific class
	dc << _T("CPGLBar ID ") << GetID() << "\n";
}
void CPGLBar::AssertValid() const
{
    // call inherited AssertValid first
    CPGLObject::AssertValid();

    // check members...
} 
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CPGLBar::CPGLBar(EType eType)
: m_eType(eType), m_dPixelInter(10)
{

}

CPGLBar& CPGLBar::operator= (const CPGLBar& b)
{
	if (&b != this)
	{
		Clear();
		
		if (b.m_pX)
			m_pX = CPGLDataPtr(b.m_pX->Clone());

		m_dPixelInter = b.m_dPixelInter;

		m_vXLabels = b.m_vXLabels;

		CPGLLine1DList::const_iterator il;
		CPGLLine1DPtr pLine1D;
		for (il = b.m_lSeries.begin(); il != b.m_lSeries.end(); ++il)
		{
			if (!*il)
				continue;

			pLine1D = boost::shared_polymorphic_cast<CPGLLine1D, CPGLNode>((*il)->MakeCopy() );
			m_lSeries.push_back(pLine1D);
		}	
	}
	return *this;
}

CPGLBar::~CPGLBar()
{
	Clear();
}

void CPGLBar::Clear()
{
	m_lSeries.clear();

	m_pX.reset();
}

void CPGLBar::PlotGfx(gfxinterface::CGfxInterface& gfx)
{
	if (!IsVisible())
		return;
	if (!IsInLayer(gfx.GetLayer()))
		return;
	if (m_lSeries.empty())	
		return;

	size_t i;
	CPGLVectorF vX0,vY0,vWidth,vHeight;
	CPGLLine1DPtr pLine;
	CPGLLine1DList::iterator il;
	CPGLDataPtr pData;

	size_t nBars = GetMinimumDataLength();
	if (!nBars)
		return;
	vX0.resize(nBars);
	vY0.resize(nBars);
	vWidth.resize(nBars);
	vHeight.resize(nBars);

	// preparing vectors
	// computing bar with...
	float dBarWidthPx, dXStart, dWidth, dWidth1;
	il = m_lSeries.begin();
	pLine = *il;
	pData = pLine->GetValues();
	ASSERT(pData);

	dBarWidthPx = GetBarWidth(gfx, nBars);

	if (m_pX)
	{
		switch (m_eType)
		{
		case ETypeSideBySide:
			for (i = 0; i<nBars;++i)
			{
				vX0[i]=static_cast<float>(gfx.WorldToBBx( m_pX->at(i) ) - m_lSeries.size()/2.0*dBarWidthPx);
			}
			break;
		case ETypeStacked:
			// first run, get X positions
			for (i = 0; i<nBars;++i)
			{
				vX0[i]=static_cast<float>(gfx.WorldToBBx( m_pX->at(i) ));
				vY0[i]=static_cast<float>(gfx.WorldToBBy(0));
			}
			break;
		}

		// second run, get width...
		if (nBars == 1)
		{
			vWidth[0]=dBarWidthPx;
		}
		else
		{
			dWidth1=static_cast<float>((fabs(vX0[1] - vX0[0]) - m_dPixelInter)/2.0);
			vWidth[0] = static_cast<float>(__min( dBarWidthPx, dWidth1)); 
			vX0[0]-=static_cast<float>(vWidth[0]/2.0);
			for (i = 1; i<nBars-1;i++)
			{
				dWidth = dWidth1;
				dWidth1 = static_cast<float>((fabs(vX0[i+1] - vX0[i]) - m_dPixelInter)/2.0);

				vWidth[i]=static_cast<float>(__min( dBarWidthPx,  __min( dWidth, dWidth1 )));

				// update vX
				vX0[i]-=static_cast<float>(vWidth[i]/2.0);
			}
			vWidth[nBars-1] = __min( dBarWidthPx, dWidth1);
			vX0[nBars-1]-=static_cast<float>(vWidth[nBars-1]/2.0);
		}
	}
	else // generating positions
	{
		dXStart = GetBarXStart(gfx, nBars, dBarWidthPx);

		switch (m_eType)
		{
		case ETypeSideBySide:
			vX0[0]=dXStart;
			vWidth[0]=dBarWidthPx;
			for (i = 1; i<nBars;++i)
			{
				vX0[i]=static_cast<float>(vX0[i-1]+m_lSeries.size()*dBarWidthPx + m_dPixelInter);
				vWidth[i]=dBarWidthPx;
			}
			break;
		case ETypeStacked:
			for (i = 0; i<nBars;++i)
			{
				vX0[i]=static_cast<float>(dXStart+i*(dBarWidthPx + m_dPixelInter));
				vY0[i]=static_cast<float>(gfx.WorldToBBy(0));
				vWidth[i]=dBarWidthPx;
			}
			break;
		}
	}

	// drawing...
	for (; il != m_lSeries.end(); il++)
	{		
		pLine = *il;
		if (!pLine)
			continue;

		pData = pLine->GetValues();
		ASSERT(pData);

		// setting pen and brush
		pLine->SetExtent( GetExtent() );
		pLine->CPGLObject::PlotGfx( gfx );	

		// update vY if stacked...
		// updating vX...
		switch(m_eType)
		{
		case ETypeSideBySide:
			for (i=0;i<nBars;++i)
			{
				vX0[i]+=dBarWidthPx;
				vHeight[i]=static_cast<float>(gfx.WorldToBBAbsy( pData->at(i) ));
				vY0[i]= static_cast<float>(gfx.WorldToBBy(0)+gfx.GetYDirectionUp()*vHeight[i]) ;
			}
			break;
		case ETypeStacked:
			for (i = 0; i<nBars;++i)
			{
				vHeight[i]=static_cast<float>(gfx.WorldToBBAbsy( pData->at(i) ));
				vY0[i] += static_cast<float>(gfx.GetYDirectionUp()*vHeight[i]);
			}
			break;
		}

		// drawing
		gfx.DrawRectStrip( vX0, vY0, vWidth, vHeight, pLine->GetERectType());
	}
}

void CPGLBar::GetUnionExtent(gfxinterface::CGfxInterface& gfx, CPGLRectD& rExtent)
{
	CPGLObject::GetUnionExtent(gfx, rExtent);

	GetExtent().SetUnion(rExtent);
	
	if (m_pX)
		rExtent.xValid=true;
	else
		rExtent.xValid=false;

	if (m_lSeries.empty())
		rExtent.yValid=false;
	else
		rExtent.yValid=true;
}

void CPGLBar::UpdateExtent(gfxinterface::CGfxInterface& gfx)
{
	using namespace std;
	// Calling base class function
	CPGLObject::UpdateExtent(gfx);

	if (m_lSeries.size()==0)
		return;

	CPGLRectD rExtent;
	CPGLLine1DPtr pLine;
	vector<float> vYSum;
	CPGLLine1DList::iterator il;
	double dMax, dMin;
	CPGLDataPtr pData;
	size_t uMin, uMax, n, i;

	if (m_pX)
	{
		m_pX->GetMinMaxIndex(uMin, uMax);
		rExtent.left = (*m_pX)[ uMin ];
		rExtent.right = (*m_pX)[ uMax ];
	}
	else
	{
		rExtent.left=0;
		rExtent.right=1;
	}

	switch(m_eType)
	{
	case ETypeSideBySide:
		il = m_lSeries.begin();
		pLine = *il;
	
		pData=pLine->GetValues();
		ASSERT(pData);
	
		rExtent.bottom = (*pData)[pData->GetMinIndex()];
		rExtent.top = (*pData)[pData->GetMaxIndex()];

		for (; il != m_lSeries.end(); ++il)
		{
			pLine = *il;
			if (!pLine)
				continue;

			pData=pLine->GetValues();	
			ASSERT(pData);

			pData->GetMinMaxIndex(uMin, uMax);
			dMin = (*pData)[uMin];
			dMax = (*pData)[uMax];
			rExtent.bottom = __min( dMin, rExtent.bottom);
			rExtent.top = __max( dMax, rExtent.top);
		}

		rExtent.bottom=__min(0, rExtent.bottom);
		break;
	case ETypeStacked:
		n=GetMinimumDataLength();
		vYSum.resize(n);
		
		il = m_lSeries.begin();
		pLine = *il;
	
		pData=pLine->GetValues();
		ASSERT(pData);
		for (i=0;i<n;i++)
			vYSum[i] = (*pData)[i];

		il++;
		for (; il != m_lSeries.end(); ++il)
		{
			pLine = *il;
			if (!pLine)
				continue;
			pData=pLine->GetValues();	
			ASSERT(pData);

			for (i=0;i<n;i++)
				vYSum[i] += (*pData)[i];
		}

		rExtent.bottom = rExtent.top = vYSum[0];
		rExtent.bottom = __min(rExtent.bottom, 0);
		for (i=1;i<n;i++)
		{
			rExtent.bottom = __min( vYSum[i], rExtent.bottom);
			rExtent.top = __max( vYSum[i], rExtent.top);
		}

		break;
	}

	SetExtent(rExtent);
}

size_t CPGLBar::GetMinimumDataLength() const
{
	if (m_lSeries.empty())
		return 0;

	CPGLLine1DPtr pLine;
	CPGLLine1DList::const_iterator il;

	il = m_lSeries.begin();
	pLine = *il;
	ASSERT(pLine);
	size_t nBars=pLine->GetValues()->size();
	il++;
	for (; il != m_lSeries.end(); ++il)
	{
		pLine = *il;
		ASSERT(pLine);
		nBars = __min( nBars, pLine->GetValues()->size());
	}

	return nBars;
}


void CPGLBar::PushLinesTop(CPGLLegendPtr pLegend)
{
	if (! pLegend)
		return;

	CPGLLine1DList::iterator il;
	for (il = m_lSeries.begin(); il != m_lSeries.end(); ++il)
	{
		pLegend->PushTop( *il );
	}
}

float CPGLBar::GetBarWidth(const gfxinterface::CGfxInterface& gfx, size_t nBars) const
{
	double dPixelWidth;

	if (m_pX)
	{
		dPixelWidth = gfx.WorldToBBAbsx( GetExtent().GetWidth() );
	}
	else
	{
		dPixelWidth=gfx.GetBBClipWidth();
	}

	switch (m_eType)
	{
	case ETypeSideBySide:
		return static_cast<float>(( dPixelWidth - (nBars-1)*m_dPixelInter ) / (m_lSeries.size()*nBars));
	case ETypeStacked:
		return static_cast<float>(( dPixelWidth - (nBars-1)*m_dPixelInter)/ (nBars));
	default:
		ASSERT(false);
		return 0;
	}
}

float CPGLBar::GetBarXStart(const gfxinterface::CGfxInterface& gfx, size_t nBars, double dBarWidthPixel) const
{
	double dPixelStart, dOffset;

	if (m_pX)
	{
		dPixelStart = gfx.WorldToBBx( GetExtent().left );
		dOffset = 0.5;
	}
	else
	{
		dPixelStart = gfx.GetBBClipLeft();
		dOffset = 0;
	}

	switch (m_eType)
	{
	case ETypeSideBySide:
		return static_cast<float>(dPixelStart - dBarWidthPixel*(1 + dOffset));
	case ETypeStacked:
		return static_cast<float>(dPixelStart - dBarWidthPixel*dOffset);
	default:
		ASSERT(false);
		return 0;
	}	
}

void CPGLBar::SetXPositions( CPGLDataPtr pXs)
{	
	m_pX = pXs; 
	PostUpdateExtent();
};

void CPGLBar::PushFront( CPGLLine1DPtr pSerie )		
{	
	if (!pSerie)
		return ;

	pSerie->SetFilled(true); 
	pSerie->SetOpen(false); 
	m_lSeries.push_front( pSerie );
};

void CPGLBar::PushTail( CPGLLine1DPtr pSerie )
{	
	if (!pSerie)
		return ;
	pSerie->SetFilled(true); 
	pSerie->SetOpen(false); 
	m_lSeries.push_back( pSerie );
};
